package com.liming.product.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.liming.aftersales.client.AfterSalesClient;
import com.liming.auth.client.UserAuthClient;
import com.liming.common.constant.SystemConstants;
import com.liming.common.enums.OperationTypeEnum;
import com.liming.common.enums.SortTypeEnum;
import com.liming.common.excel.ExcelUtil;
import com.liming.common.exception.BusinessException;
import com.liming.common.pojo.dto.PageDTO;
import com.liming.common.pojo.dto.authinfo.UserActivityAddDTO;
import com.liming.common.pojo.dto.product.*;
import com.liming.common.pojo.dto.stock.InitStockDTO;
import com.liming.common.pojo.dto.stock.StockPageQuery;
import com.liming.common.pojo.entity.BaseEntity;
import com.liming.common.pojo.entity.product.Product;
import com.liming.common.pojo.vo.PageVO;
import com.liming.common.pojo.vo.product.ProductPriceVO;
import com.liming.common.pojo.vo.product.openfeign.FeignMiniProductDetailVO;
import com.liming.common.pojo.vo.product.openfeign.FeignMiniProductVO;
import com.liming.common.pojo.vo.product.openfeign.FeignProductVO;
import com.liming.common.pojo.vo.review.MiniProductCommentVO;
import com.liming.common.util.BeanUtils;
import com.liming.common.util.PageUtils;
import com.liming.common.util.ThreadLocalUtils;
import com.liming.common.util.TimeUtils;
import com.liming.common.util.mybatisplus.MPUtil;
import com.liming.product.enums.ProductShelfEnum;
import com.liming.product.excel.ProductExcelListener;
import com.liming.product.mapper.ProductMircoMapper;
import com.liming.product.service.BrandMircoService;
import com.liming.product.service.CategoryMircoService;
import com.liming.product.service.ProductMircoService;
import com.liming.stock.client.StockClient;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import static com.liming.common.constant.DBConstants.PRODUCT_ZERO_QUANTITY;
import static com.liming.common.constant.DBConstants.WAREHOUSE_DEFAULT_ID;

/**
 * @author liming
 * @date 2025/1/16
 */
@Slf4j
@Service
@AllArgsConstructor
public class ProductMircoServiceImpl extends ServiceImpl<ProductMircoMapper, Product>
        implements ProductMircoService, MPUtil<Product> {

    private final UserAuthClient userAuthClient;
    private final BrandMircoService brandMircoService;
    private final CategoryMircoService categoryMircoService;
    private final StockClient stockClient;
    private final AfterSalesClient afterSalesClient;

    private static void judgePrice(ProductPageQuery query) {
        Integer lowPrice = query.getLowPrice();
        Integer highPrice = query.getHighPrice();
        if (ObjectUtil.isNotNull(lowPrice)) {
            lowPrice *= 100;
        }
        query.setLowPrice(lowPrice);
        if (ObjectUtil.isNotNull(highPrice)) {
            highPrice *= 100;
        }
        query.setHighPrice(highPrice);
    }

    @Override
    public PageVO<FeignProductVO> page(ProductPageQuery dto) {
        judgePrice(dto);

        Long categoryId = dto.getCategoryId();
        // 判断该分类ID 是否是其他分类的父级ID 如果是其他分类的父级ID 就把其他分类的ID也查询出来
        Collection<Long> categoryIds = categoryMircoService.parentAndChildren(categoryId);

        Map<Long, Integer> productIdAndQuantity = stockClient.allProductAvailableQuantityMap();
        IPage<FeignProductVO> page = lambdaQuery()
                .like(StrUtil.isNotBlank(dto.getName()), Product::getName, dto.getName())
                .like(StrUtil.isNotBlank(dto.getModelNumber()), Product::getModelNumber, dto.getModelNumber())
                .ge(ObjectUtil.isNotNull(dto.getLowPrice()), Product::getPrice, dto.getLowPrice())
                .le(ObjectUtil.isNotNull(dto.getHighPrice()), Product::getPrice, dto.getHighPrice())
                .eq(ObjectUtil.isNotNull(dto.getBrandId()), Product::getBrandId, dto.getBrandId())
                .eq(ObjectUtil.isNotNull(dto.getCategoryId()), Product::getCategoryId, dto.getCategoryId())
                .or()
                .in(CollUtil.isNotEmpty(categoryIds), Product::getCategoryId, categoryIds)
                .eq(ObjectUtil.isNotNull(dto.getOnShelf()), Product::getOnShelf, dto.getOnShelf())
                .page(setPage(new PageDTO(dto.getCurrent(), dto.getSize())))
                .convert(product -> {
                    Integer availableQuantity = productIdAndQuantity.get(product.getId());
                    FeignProductVO vo = new FeignProductVO();
                    org.springframework.beans.BeanUtils.copyProperties(product, vo);
                    vo.setQuantity(availableQuantity);
                    return vo;
                });
        PageVO<FeignProductVO> vo = new PageVO<>();
        org.springframework.beans.BeanUtils.copyProperties(page, vo);
        return vo;
    }

    @Override
    public FeignProductVO get(Long id) {
        Product product = getById(id);
        Assert.notNull(product, () -> new BusinessException("商品不存在"));
        FeignProductVO vo = new FeignProductVO();
        org.springframework.beans.BeanUtils.copyProperties(product, vo);
        return vo;
    }

    @Override
    public Map<Long, Product> idProductMapByLikeName(String name) {
        return lambdaQuery().like(StrUtil.isNotBlank(name), Product::getName, name).list()
                .stream().collect(Collectors.toMap(BaseEntity::getId, product -> product));
    }

    @Override
    public List<FeignProductVO> listProductByIds(List<Long> ids) {
        List<Product> productList = lambdaQuery().in(Product::getId, ids).list();
        Map<Long, Integer> productIdAndQuantity = stockClient.allProductAvailableQuantityMap();
        return productList.stream().map(product -> {
            Integer availableQuantity = productIdAndQuantity.get(product.getId());
            FeignProductVO vo = new FeignProductVO();
            org.springframework.beans.BeanUtils.copyProperties(product, vo);
            vo.setQuantity(availableQuantity);
            return vo;
        }).toList();
    }

    @Override
    public Map<Long, Integer> idAndPriceMap(List<Long> productIds) {
        return lambdaQuery().in(Product::getId, productIds).list().stream().collect(Collectors.toMap(Product::getId, Product::getPrice));
    }

    @Override
    @GlobalTransactional
    public Boolean add(AddProductDTO dto) {
        Product save = new Product();
        org.springframework.beans.BeanUtils.copyProperties(dto, save);
        // 判断
        judgeNameAndModelNum(save);
        // 商品默认 - 下架
        save.setOnShelf(ProductShelfEnum.OFF_SHELF.getCode());
        // 商品新添加时销量为0
        save.setSalesCount(PRODUCT_ZERO_QUANTITY);
        Assert.isTrue(save(save), () -> new BusinessException("商品保存失败"));
        String id = ThreadLocalUtils.get(SystemConstants.ID);
        CompletableFuture.runAsync(() -> {
            InitStockDTO stockDTO = new InitStockDTO();
            stockDTO.setOperatorId(id);
            stockDTO.setProductId(save.getId());
            stockDTO.setWarehouseId(WAREHOUSE_DEFAULT_ID);
            Assert.isTrue(stockClient.initStock(stockDTO), () -> new BusinessException("库存保存失败"));
        });
        return true;
    }

    @Override
    public Boolean importProduct(MultipartFile file) {
        ExcelUtil.defaultRead(file, ProductImportDTO.class, new ProductExcelListener(this, brandMircoService, categoryMircoService, stockClient));
        return true;
    }

    @Override
    public Boolean updateProduct(UpdateProductDTO dto) {
        Product update = new Product();
        org.springframework.beans.BeanUtils.copyProperties(dto, update);
        // 判断
        judgeNameAndModelNum(update);
        Assert.isTrue(updateById(update), () -> new BusinessException("商品更新失败"));
        return true;
    }

    private void judgeNameAndModelNum(Product update) {
        // 判断商品名称是否重复
        Product name = lambdaQuery()
                .eq(ObjectUtil.isNotNull(update.getName()), Product::getName, update.getName())
                .ne(ObjectUtil.isNotNull(update.getId()), Product::getId, update.getId())
                .one();
        Assert.isNull(name, () -> new BusinessException("商品已存在"));
        // 判断商品型号是否重复
        Product modelNumber = lambdaQuery()
                .eq(ObjectUtil.isNotNull(update.getModelNumber()), Product::getModelNumber, update.getModelNumber())
                .ne(ObjectUtil.isNotNull(update.getId()), Product::getId, update.getId())
                .one();
        Assert.isNull(modelNumber, () -> new BusinessException("商品型号已存在"));
    }

    @Override
    public Boolean updateOnShelf(Long id) {
        Product prod = getById(id);
        Assert.notNull(prod, () -> new BusinessException("商品不存在"));
        Product update = new Product();
        update.setId(id);
        if (ObjectUtil.equal(prod.getOnShelf(), ProductShelfEnum.ON_SHELF.getCode())) {
            update.setOnShelf(ProductShelfEnum.OFF_SHELF.getCode());
        } else {
            update.setOnShelf(ProductShelfEnum.ON_SHELF.getCode());
        }
        Assert.isTrue(updateById(update), () -> new BusinessException("商品状态更新失败"));
        return true;
    }

    @Override
    public ProductPriceVO getPrice(Long id) {
        Product product = getById(id);
        Assert.notNull(product, () -> new BusinessException("商品不存在"));
        return new ProductPriceVO(product);
    }

    @Override
    public List<Long> productIdsByStockQuery(StockPageQuery query) {
        Collection<Long> categoryIds = categoryMircoService.parentAndChildren(query.getCategoryId());
        return lambdaQuery()
                .like(StrUtil.isNotBlank(query.getProductName()), Product::getName, query.getProductName())
                .like(StrUtil.isNotBlank(query.getModelNumber()), Product::getModelNumber, query.getModelNumber())
                .eq(ObjectUtil.isNotNull(query.getBrandId()), Product::getBrandId, query.getBrandId())
                .and(ObjectUtil.isNotNull(query.getCategoryId()), wrapper -> wrapper
                        .eq(ObjectUtil.isNotNull(query.getCategoryId()), Product::getCategoryId, query.getCategoryId())
                        .or()
                        .in(CollUtil.isNotEmpty(categoryIds), Product::getCategoryId, categoryIds))
                .list()
                .stream()
                .map(BaseEntity::getId)
                .toList();
    }

    @Override
    public List<FeignProductVO> stagnantProductCount() {
        List<Product> productList = lambdaQuery().list()
                .stream()
                .filter(product -> ObjectUtil.isNotNull(product.getSaleTime()))
                .filter(product -> TimeUtils.getDaysBetween(product.getSaleTime(), LocalDateTime.now()) >= 30L)
                .toList();
        if (CollUtil.isEmpty(productList)) {
            return CollUtil.empty(null);
        }
        Map<Long, Integer> productIdAndQuantity = stockClient.allProductAvailableQuantityMap();
        return productList.stream().map(product -> {
            FeignProductVO feignProductVO = new FeignProductVO();
            feignProductVO.setQuantity(productIdAndQuantity.get(product.getId()));
            org.springframework.beans.BeanUtils.copyProperties(product, feignProductVO);
            return feignProductVO;
        }).toList();
    }

    @Override
    public List<FeignMiniProductVO> recommendProduct() {
        List<Product> list = lambdaQuery().eq(Product::getOnShelf, ProductShelfEnum.ON_SHELF.getCode()).list();
        long userId = Long.parseLong(ThreadLocalUtils.get(SystemConstants.ID));
        // 如果用户有下单支付，则优先推荐下单支付的商品的品牌和分类。
        CompletableFuture<Map<Integer, List<Long>>> orderMapFuture = CompletableFuture.supplyAsync(() -> userAuthClient.activityBrandAndCategoryListMap(userId, OperationTypeEnum.ORDER_PAY.getCode()));
        // 其次推荐用户加入购物车的商品的品牌和分类。
        CompletableFuture<Map<Integer, List<Long>>> cartMapFuture = CompletableFuture.supplyAsync(() -> userAuthClient.activityBrandAndCategoryListMap(userId, OperationTypeEnum.ADD_CART.getCode()));
        // 最后推荐用户浏览的商品的品牌和分类。
        CompletableFuture<Map<Integer, List<Long>>> browseMapFuture = CompletableFuture.supplyAsync(() -> userAuthClient.activityBrandAndCategoryListMap(userId, OperationTypeEnum.BROWSE.getCode()));
        CompletableFuture.allOf(orderMapFuture, cartMapFuture, browseMapFuture).join();
        // 1
        Map<Integer, List<Long>> orderMap = orderMapFuture.join();
        List<Long> orderBrandList = orderMap.getOrDefault(1, Collections.emptyList());
        List<Long> orderCategoryList = orderMap.getOrDefault(2, Collections.emptyList());
        // 2
        Map<Integer, List<Long>> cartMap = cartMapFuture.join();
        List<Long> cartBrandList = cartMap.getOrDefault(1, Collections.emptyList());
        List<Long> cartCategoryList = cartMap.getOrDefault(2, Collections.emptyList());
        // 3
        Map<Integer, List<Long>> browseMap = browseMapFuture.join();
        List<Long> browseBrandList = browseMap.getOrDefault(1, Collections.emptyList());
        List<Long> browseCategoryList = browseMap.getOrDefault(2, Collections.emptyList());
        // 再根据商品的分数排序（定时任务计算）
        list.sort(Comparator.<Product>comparingInt(product -> {
            if (CollUtil.contains(orderBrandList, product.getBrandId())) {
                return -2;
            } else if (CollUtil.contains(orderCategoryList, product.getCategoryId())) {
                return -1;
            } else {
                return 0;
            }
        }).thenComparingInt(product -> {
            if (CollUtil.contains(cartBrandList, product.getBrandId())) {
                return -2;
            } else if (CollUtil.contains(cartCategoryList, product.getCategoryId())) {
                return -1;
            } else {
                return 0;
            }
        }).thenComparingInt(product -> {
            if (CollUtil.contains(browseBrandList, product.getBrandId())) {
                return -2;
            } else if (CollUtil.contains(browseCategoryList, product.getCategoryId())) {
                return -1;
            } else {
                return 0;
            }
        }).thenComparing(Product::getActivityScore));
        CompletableFuture.runAsync(() -> {
            UserActivityAddDTO userActivityAddDTO = new UserActivityAddDTO();
            userActivityAddDTO.setUserId(Long.parseLong(ThreadLocalUtils.get(SystemConstants.ID)));
            userActivityAddDTO.setOperationType(OperationTypeEnum.BROWSE_RECOMMEND.getCode());
            userAuthClient.addUserActivity(userActivityAddDTO);
        });
        return list
                .stream()
                .limit(6)
                .map(product -> BeanUtils.toBean(product, FeignMiniProductVO.class))
                .toList();
    }

    @Override
    public FeignMiniProductDetailVO miniDetail(Long id) {
        Product product = lambdaQuery().eq(Product::getId, id).one();
        FeignMiniProductDetailVO feignResult = BeanUtils.toBean(product, FeignMiniProductDetailVO.class);
        // 查询商品评论
        List<MiniProductCommentVO> comments = afterSalesClient.miniProductReviews(product.getId());
        feignResult.setCommentCount(comments.size());
        feignResult.setGoodRatingPercentage(comments.stream().map(MiniProductCommentVO::getRating).filter(rating -> rating >= 3.0F).toList().size() * 100.0F / comments.size());
        feignResult.setRating(0.0F);
        if (CollUtil.isNotEmpty(comments)) {
            feignResult.setRating(comments.stream().map(MiniProductCommentVO::getRating).reduce(0F, Float::sum) / comments.size());
        }
        feignResult.setComments(comments.subList(0, Math.min(comments.size(), 2)));
        return feignResult;
    }

    @Override
    public PageVO<FeignMiniProductVO> miniPage(MiniProductPageQuery query) {
        int lowPrice = 0;
        int highPrice = Integer.MAX_VALUE;
        if (StrUtil.isNotBlank(query.getPriceRange())) {
            String[] range = query.getPriceRange().split("~");
            lowPrice = Integer.parseInt(range[0]) * 100;
            if (ObjectUtil.notEqual(range[1], "max")) {
                highPrice = Integer.parseInt(range[1]) * 100;
            }
        }
        // 查询商品
        String fullQuery = query.getFullQuery();
        IPage<Product> rawPage = lambdaQuery()
                .and(StrUtil.isNotBlank(fullQuery), wrapper -> wrapper
                        .like(Product::getName, fullQuery)
                        .or().like(Product::getModelNumber, fullQuery)
                        .or().like(Product::getBrandName, fullQuery)
                        .or().like(Product::getCategoryName, fullQuery)
                        .or().like(Product::getParameters, fullQuery)
                        .or().like(Product::getFeatures, fullQuery))
                .eq(ObjectUtil.isNotNull(query.getBrandId()), Product::getBrandId, query.getBrandId())
                .in(ObjectUtil.isNotNull(query.getCategoryId()), Product::getCategoryId, categoryMircoService.parentAndChildren(query.getCategoryId()))
                .between(StrUtil.isNotBlank(query.getPriceRange()), Product::getPrice, lowPrice, highPrice)
                .orderBy(ObjectUtil.isNotNull(query.getSortType()) && SortTypeEnum.isPriceSortType(query.getSortType()), ObjectUtil.equal(query.getSortType(), SortTypeEnum.PRICE_ASC.getCode()), Product::getPrice)
                .orderByDesc(ObjectUtil.isNotNull(query.getSortType()) && SortTypeEnum.isSalesSortType(query.getSortType()), Product::getSalesCount)
                .eq(Product::getOnShelf, ProductShelfEnum.ON_SHELF.getCode())
                .page(setPage(query));
        return PageUtils.convert(rawPage, product -> BeanUtils.toBean(product, FeignMiniProductVO.class));
    }

    @Override
    public Map<Integer, List<Long>> brandAndCategoryIdsMapByProductIds(List<Long> keyIdList) {
        Map<Integer, List<Long>> map = new HashMap<>();
        if (CollUtil.isEmpty(keyIdList)) {
            return map;
        }
        List<Product> productList = lambdaQuery().in(BaseEntity::getId, keyIdList).list();
        List<Long> brandList = productList.stream().map(Product::getBrandId).collect(Collectors.toSet()).stream().toList();
        List<Long> categoryList = productList.stream().map(Product::getCategoryId).collect(Collectors.toSet()).stream().toList();
        map.put(1, brandList);
        map.put(2, categoryList);
        return map;
    }

    @Override
    public Boolean increaseSalesByMap(Map<Long, Integer> productIdAndQuantity) {
        for (Map.Entry<Long, Integer> entry : productIdAndQuantity.entrySet()) {
            Long key = entry.getKey();
            Integer salesCount = entry.getValue();
            boolean update = lambdaUpdate()
                    .eq(BaseEntity::getId, key)
                    .setSql("sales_count = sales_count + " + salesCount)
                    .update();
            Assert.isTrue(update, () -> new BusinessException("商品" + entry.getKey() + "销量更新失败"));
        }
        return true;
    }

    @Override
    public void updateSalesTimeByIds(List<Long> productIds) {
        lambdaUpdate()
                .in(BaseEntity::getId, productIds)
                .set(Product::getSaleTime, LocalDateTime.now())
                .update();
    }
}
